<template>
    <view class="lime-signature" v-if="show" :style="[canvasStyle, styles]" ref="limeSignature">
        <!-- #ifndef APP-VUE || APP-NVUE -->
        <canvas v-if="useCanvas2d" class="lime-signature__canvas" :id="canvasId" type="2d"
            :disableScroll="disableScroll" @touchstart="touchStart" @touchmove="touchMove"
            @touchend="touchEnd"></canvas>
        <canvas v-else :disableScroll="disableScroll" class="lime-signature__canvas" :canvas-id="canvasId"
            :id="canvasId" :width="canvasWidth" :height="canvasHeight" @touchstart="touchStart" @touchmove="touchMove"
            @touchend="touchEnd" @mousedown="touchStart" @mousemove="touchMove" @mouseup="touchEnd"></canvas>
        <canvas v-if="showOffscreen" class="offscreen" canvas-id="offscreen" id="offscreen"
            :style="'width:' + offscreenSize[0] + 'px;height:' + offscreenSize[1] + 'px'" :width="offscreenSize[0]"
            :height="offscreenSize[1]">
        </canvas>
        <view v-if="showMask" class="mask" @touchstart="touchStart" @touchmove.stop.prevent="touchMove"
            @touchend="touchEnd"></view>
        <!-- #endif -->
        <!-- #ifdef APP-VUE -->
        <view :id="canvasId" :disableScroll="disableScroll" :rparam="param" :change:rparam="sign.update"
            :rclear="rclear" :change:rclear="sign.clear" :rundo="rundo" :rredo="rredo" :change:rredo="sign.redo"
            :change:rundo="sign.undo" :rsave="rsave" :rmask="rmask" :change:rsave="sign.save" :change:rmask="sign.mask"
            :rdestroy="rdestroy" :change:rdestroy="sign.destroy" :rempty="rempty" :change:rempty="sign.isEmpty">
        </view>
        <!-- #endif -->
        <!-- #ifdef APP-NVUE -->
        <web-view src="/uni_modules/lime-signature/hybrid/html/index.html" class="lime-signature__canvas" ref="webview"
            @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage"></web-view>
        <!-- #endif -->
    </view>
</template>

<script>
// #ifndef APP-NVUE
import {
    canIUseCanvas2d,
    wrapEvent,
    requestAnimationFrame,
    sleep,
    isTransparent
} from './utils'
import {
    Signature
} from './signature.js'
// import {Signature} from '@signature';
import {
    uniContext,
    createImage,
    toDataURL
} from './context'
// #endif
import props from './props';
import {
    base64ToPath,
    getRect
} from './utils'
import { nextTick } from 'vue';

/**
 * LimeSignature 手写板签名
 * @description 手写板签名插件：一款能跑在uniapp各端中的签名插件，支持横屏、背景色、笔画颜色、笔画大小等功能,可生成有内容的区域，减小图片尺寸，节省空间。
 * @tutorial https://ext.dcloud.net.cn/plugin?id=4354
 * @property {Number} penSize 画笔大小
 * @property {Number} minLineWidth 线条最小宽
 * @property {Number} maxLineWidth 线条最大宽 
 * @property {String} penColor 画笔颜色 
 * @property {String} backgroundColor 背景颜色,不填则为透明
 * @property {type} 指定 canvas 类型
 * @value 2d canvas 2d 
 * @value '' 非 canvas 2d 旧接口，微信不再维护
 * @property {Boolean} openSmooth 模拟笔锋 
 * @property {Number} beforeDelay 延时初始化，在放在弹窗里可以使用 （毫秒）  
 * @property {Number} maxHistoryLength 限制历史记录数，即最大可撤销数，传入0则关闭历史记录功能 
 * @property {Boolean} landscape 横屏，使用后在最后生成图片时会图片旋转90度
 * @property {Boolean} disableScroll 当在写字时，禁止屏幕滚动以及下拉刷新，nvue无效
 * @property {Boolean} boundingBox 只生成内容区域，即未画部分不生成，有性能的损耗
 */
export default {
    props,
    data() {
        return {
            canvasWidth: null,
            canvasHeight: null,
            offscreenWidth: null,
            offscreenHeight: null,
            useCanvas2d: true,
            show: true,
            offscreenStyles: '',
            showMask: false,
            showOffscreen: false,
            isPC: false,
            // #ifdef APP-PLUS
            rclear: 0,
            rdestroy: 0,
            rundo: 0,
            rredo: 0,
            rsave: JSON.stringify({
                n: 0,
                fileType: 'png',
                quality: 1,
                destWidth: 0,
                destHeight: 0,
            }),
            rmask: JSON.stringify({
                n: 0,
                destWidth: 0,
                destHeight: 0,
            }),
            rempty: 0,
            risEmpty: true,
            toDataURL: null,
            tempFilePath: [],
            // #endif
        }
    },
    computed: {
        canvasId() {
            // #ifdef VUE2
            return `lime-signature${this._uid}`
            // #endif
            // #ifdef VUE3
            return `lime-signature${this._.uid}`
            // #endif
        },
        offscreenId() {
            return this.canvasId + 'offscreen'
        },
        offscreenSize() {
            const {
                offscreenWidth,
                offscreenHeight
            } = this
            return this.landscape ? [offscreenHeight, offscreenWidth] : [offscreenWidth, offscreenHeight]
        },
        canvasStyle() {
            const {
                canvasWidth,
                canvasHeight,
                backgroundColor
            } = this
            return {
                width: canvasWidth && (canvasWidth + 'px'),
                height: canvasHeight && (canvasHeight + 'px'),
                background: backgroundColor
            }
        },
        param() {
            const {
                penColor,
                penSize,
                backgroundColor,
                backgroundImage,
                landscape,
                boundingBox,
                openSmooth,
                minLineWidth,
                maxLineWidth,
                minSpeed,
                maxWidthDiffRate,
                maxHistoryLength,
                disableScroll,
                disabled
            } = this
            return JSON.parse(JSON.stringify({
                penColor,
                penSize,
                backgroundColor,
                backgroundImage,
                landscape,
                boundingBox,
                openSmooth,
                minLineWidth,
                maxLineWidth,
                minSpeed,
                maxWidthDiffRate,
                maxHistoryLength,
                disableScroll,
                disabled
            }))
        }
    },
    // #ifdef APP-NVUE
    watch: {
        param(v) {
            this.$refs.webview.evalJS(`update(${JSON.stringify(v)})`)
        }
    },
    // #endif
    // #ifndef APP-PLUS
    created() {
        const {
            platform
        } = uni.getSystemInfoSync()
        this.isPC = /windows|mac/.test(platform)
        this.useCanvas2d = this.type == '2d' && canIUseCanvas2d() && !this.isPC
        // #ifndef H5
        this.showMask = this.isPC
        // #endif

    },
    // #endif
    // #ifndef APP-PLUS
    async mounted() {
        if (this.beforeDelay) {
            await sleep(this.beforeDelay)
        }
        const config = await this.getContext()
        this.signature = new Signature(config)
        this.canvasEl = this.signature.canvas.get('el')
        this.offscreenWidth = this.canvasWidth = this.signature.canvas.get('width')
        const systemInfo = uni.getSystemInfoSync();
        this.offscreenHeight = this.canvasHeight = systemInfo.screenHeight - 100

        this.stopWatch = this.$watch('param', (v) => {
            this.signature.pen.setOption(v)
        }, {
            immediate: true
        })
    },
    // #endif
    // #ifndef APP-PLUS
    // #ifdef VUE3
    beforeUnmount() {
        this.stopWatch && this.stopWatch()
        this.signature.destroy()
        this.signature = null
        this.show = false;
        // #ifdef APP-VUE || APP-NVUE
        this.rdestroy++
        // #endif
    },
    // #endif
    // #ifdef VUE2
    beforeDestroy() {
        this.stopWatch && this.stopWatch()
        this.signature.destroy()
        this.show = false;
        this.signature = null
        // #ifdef APP-VUE || APP-NVUE
        this.rdestroy++
        // #endif
    },
    // #endif
    // #endif
    methods: {
        // #ifdef MP-QQ
        // toJSON() { return this },
        // #endif
        // #ifdef APP-PLUS
        onPageFinish() {
            this.$refs.webview.evalJS(`update(${JSON.stringify(this.param)})`)
        },
        onMessage(e = {}) {
            const {
                detail: {
                    data: [res]
                }
            } = e
            if (res.event?.save) {
                this.toDataURL = res.event.save
            }
            if (res.event?.changeSize) {
                const {
                    width,
                    height
                } = res.event.changeSize
            }
            if (res.event.hasOwnProperty('isEmpty')) {
                this.risEmpty = res.event.isEmpty
            }
            if (res.event?.file) {
                this.tempFilePath.push(res.event.file)
                if (this.tempFilePath.length > 7) {
                    this.tempFilePath.shift()
                }
                return
            }
            if (res.event?.success) {
                if (res.event.success) {
                    this.tempFilePath.push(res.event.success)
                    if (this.tempFilePath.length > 8) {
                        this.tempFilePath.shift()
                    }
                    this.toDataURL = this.tempFilePath.join('')
                    this.tempFilePath = []
                } else {
                    this.$emit('fail', 'canvas no data')
                }
                return
            }
        },
        // #endif
        redo() {
            // #ifdef APP-VUE || APP-NVUE
            this.rredo += 1
            // #endif
            // #ifdef APP-NVUE
            this.$refs.webview.evalJS(`redo()`)
            // #endif
            // #ifndef APP-VUE
            if (this.signature)
                this.signature.redo()
            // #endif
        },
        restore() {
            this.redo()
        },
        undo() {
            // #ifdef APP-VUE || APP-NVUE
            this.rundo += 1
            // #endif
            // #ifdef APP-NVUE
            this.$refs.webview.evalJS(`undo()`)
            // #endif
            // #ifndef APP-VUE
            if (this.signature)
                this.signature.undo()
            // #endif
        },
        clear() {
            // #ifdef APP-VUE || APP-NVUE
            this.rclear += 1
            // #endif
            // #ifdef APP-NVUE
            this.$refs.webview.evalJS(`clear()`)
            // #endif
            // #ifndef APP-VUE
            if (this.signature)
                this.signature.clear()
            // #endif
        },
        isEmpty() {
            // #ifdef APP-NVUE
            this.$refs.webview.evalJS(`isEmpty()`)
            // #endif
            // #ifdef APP-VUE || APP-NVUE
            this.rempty += 1
            // #endif
            // #ifndef APP-VUE || APP-NVUE
            return this.signature.isEmpty()
            // #endif
        },
        async canvasToMaskPath(param = {}) {
            const isEmpty = this.isEmpty()
            // #ifdef APP-NVUE
            this.$refs.webview.evalJS(`mask(${JSON.stringify(param)})`)
            // #endif
            // #ifdef APP-VUE || APP-NVUE
            const stopURLWatch = this.$watch('toDataURL', (v, n) => {
                if (v && v !== n) {
                    // if(param.pathType == 'url') {
                    base64ToPath(v).then(res => {
                        param.success({
                            tempFilePath: res,
                            isEmpty: this.risEmpty
                        })
                    })
                    // } else {
                    // 	param.success({tempFilePath: v,isEmpty: this.risEmpty })
                    // }
                    this.toDataURL = ''
                }
                stopURLWatch && stopURLWatch()
            })
            const {
                fileType,
                quality
            } = param
            const rmask = JSON.parse(this.rmask)
            rmask.n++
            rmask.destWidth = param.destWidth ?? 0
            rmask.destHeight = param.destHeight ?? 0
            // rmask.fileType = fileType
            // rmask.quality = quality
            this.rmask = JSON.stringify(rmask)
            // #endif
            // #ifndef APP-VUE || APP-NVUE
            this.showOffscreen = true

            let width = this.signature.canvas.get('width')
            let height = this.signature.canvas.get('height')
            let {
                pixelRatio
            } = uni.getSystemInfoSync()
            if (this.useCanvas2d) {
                this.offscreenWidth = width * pixelRatio
                this.offscreenHeight = height * pixelRatio
            } else {
                this.offscreenWidth = width
                this.offscreenHeight = height
            }
            await sleep(100)
            const context = uni.createCanvasContext('offscreen', this)
            const size = Math.max(this.offscreenWidth, this.offscreenHeight)
            const success = (success) => param.success && param.success(success)
            const fail = (fail) => param.fail && param.fail(fail)

            this.signature.pen.getMaskedImageData((imageData) => {
                let canvasPutImageData = (options, comp) => {
                    if (uni.canvasPutImageData) {
                        uni.canvasPutImageData(options, comp)
                    } else if (context.putImageData) {
                        context.putImageData(options)
                    }
                }
                canvasPutImageData({
                    canvasId: 'offscreen',
                    x: 0,
                    y: 0,
                    width: width,
                    height: height,
                    data: imageData,
                    fail(err) {
                        fail(err)
                    },
                    success: (re) => {
                        toDataURL('offscreen', this, param).then((res) => {
                            context.restore()
                            context.clearRect(0, 0, size, size)
                            this.offscreenWidth = width
                            this.offscreenHeight = height
                            this.showOffscreen = false
                            success({
                                tempFilePath: res,
                                isEmpty
                            })
                        })
                    }
                }, this)

            })
            // #endif
        },
        canvasToTempFilePath(param = {}) {

            const isEmpty = this.isEmpty()
            // #ifdef APP-NVUE
            this.$refs.webview.evalJS(`save(${JSON.stringify(param)})`)
            // #endif
            // #ifdef APP-VUE || APP-NVUE
            const stopURLWatch = this.$watch('toDataURL', (v, n) => {
                if (v && v !== n) {
                    if (this.preferToDataURL) {
                        param.success({
                            tempFilePath: v,
                            isEmpty: this.risEmpty
                        })
                    } else {
                        base64ToPath(v).then(res => {
                            param.success({
                                tempFilePath: res,
                                isEmpty: this.risEmpty
                            })
                        })
                    }
                    this.toDataURL = ''
                }
                stopURLWatch && stopURLWatch()
            })
            const {
                fileType,
                quality
            } = param
            const rsave = JSON.parse(this.rsave)
            rsave.n++
            rsave.fileType = fileType
            rsave.quality = quality
            rsave.destWidth = param.destWidth ?? 0
            rsave.destHeight = param.destHeight ?? 0
            this.rsave = JSON.stringify(rsave)
            // #endif
            // #ifndef APP-VUE || APP-NVUE
            const useCanvas2d = this.useCanvas2d
            const success = (success) => param.success && param.success(success)
            const fail = (err) => param.fail && param.fail(err)
            const {
                canvas
            } = this.signature.canvas.get('el')
            const {
                backgroundColor,
                landscape,
                boundingBox
            } = this
            let width = this.signature.canvas.get('width')
            let height = this.signature.canvas.get('height')
            let x = 0
            let y = 0
            const devtools = uni.getSystemInfoSync().platform == 'devtools'
            let preferToDataURL = this.preferToDataURL
            let scale = 1
            // #ifdef MP-TOUTIAO
            scale = devtools ? uni.getSystemInfoSync().pixelRatio : scale
            // 由于抖音不支持canvasToTempFilePath故优先使用createOffscreenCanvas
            preferToDataURL = true
            // #endif
            const canvasToTempFilePath = async (image) => {
                const createCanvasContext = () => {
                    const useOffscreen = (useCanvas2d && !!uni.createOffscreenCanvas && preferToDataURL)
                    if (useOffscreen && !devtools) {
                        const offCanvas = uni.createOffscreenCanvas({
                            type: '2d'
                        });
                        offCanvas.width = this.offscreenSize[0] * scale
                        offCanvas.height = this.offscreenSize[1] * scale
                        const context = offCanvas.getContext("2d");
                        return [context, offCanvas]
                    } else {
                        const context = uni.createCanvasContext('offscreen', this)
                        return [context]
                    }
                }

                if (boundingBox && !this.isPC || landscape || backgroundColor && !isTransparent(backgroundColor)) {

                    this.showOffscreen = true
                    await sleep(100)
                    const [context, offCanvas] = createCanvasContext()
                    context.save()
                    context.setTransform(1, 0, 0, 1, 0, 0)
                    if (landscape) {
                        context.translate(0, width * scale)
                        context.rotate(-Math.PI / 2)
                    }
                    if (backgroundColor && !isTransparent(backgroundColor)) {
                        context.fillStyle = backgroundColor
                        context.fillRect(0, 0, width, height)
                    }
                    if (offCanvas) {
                        const img = canvas.createImage();
                        img.src = image
                        img.onload = () => {
                            context.drawImage(img, 0, 0, width * scale, height * scale);
                            const tempFilePath = offCanvas.toDataURL()
                            this.showOffscreen = false
                            success({
                                tempFilePath,
                                isEmpty
                            })
                        }

                    } else {
                        context.drawImage(image, 0, 0, width * scale, height * scale);
                        context.draw(false, () => {
                            toDataURL('offscreen', this, param).then((res) => {
                                const size = Math.max(width, height)
                                context.restore()
                                context.clearRect(0, 0, size, size)
                                this.showOffscreen = false
                                success({
                                    tempFilePath: res,
                                    isEmpty
                                })
                            })
                        })
                    }
                } else {
                    success({
                        tempFilePath: image,
                        isEmpty
                    })
                }
            }
            const next = async () => {
                if (this.offscreenWidth != width || this.offscreenHeight != height) {
                    this.offscreenWidth = width
                    this.offscreenHeight = height
                    await sleep(100)
                }

                // #ifndef MP-WEIXIN
                const param = {
                    x,
                    y,
                    width,
                    height,
                    canvas,
                    preferToDataURL
                }
                // #endif

                // #ifdef MP-WEIXIN
                const param = {
                    x,
                    y,
                    width,
                    height,
                    canvas: useCanvas2d ? canvas : null,
                    preferToDataURL
                }
                // #endif
                toDataURL(this.canvasId, this, param).then(canvasToTempFilePath).catch(fail)
            }
            // PC端小程序获取不到 ImageData 数据，长度为0
            if (boundingBox && !this.isPC) {
                this.signature.getContentBoundingBox(async res => {
                    this.offscreenWidth = width = res.width
                    this.offscreenHeight = height = res.height

                    x = res.startX
                    y = res.startY

                    next()
                })
            } else {
                next()
            }
            // #endif
        },
        // #ifndef APP-PLUS
        getContext() {
            return getRect(`#${this.canvasId}`, {
                context: this,
                type: this.useCanvas2d ? 'fields' : 'boundingClientRect'
            }).then(res => {
                if (res) {
                    let {
                        width,
                        height,
                        node: canvas,
                        left,
                        top,
                        right
                    } = res
                    let {
                        pixelRatio
                    } = uni.getSystemInfoSync()
                    let context;
                    if (canvas) {
                        context = canvas.getContext('2d')
                        canvas.width = width * pixelRatio;
                        canvas.height = height * pixelRatio;
                    } else {
                        pixelRatio = 1
                        context = uniContext(this.canvasId, this)
                        canvas = {
                            getContext: (type) => type == '2d' ? context : null,
                            createImage,
                            toDataURL: () => toDataURL(this.canvasId, this),
                            requestAnimationFrame
                        }
                    }
                    // 支付宝小程序 使用stroke有个默认背景色
                    context.clearRect(0, 0, width, height)
                    return {
                        left,
                        top,
                        right,
                        width,
                        height,
                        context,
                        canvas,
                        pixelRatio
                    };
                }
            })
        },
        getTouch(e) {
            if (this.isPC && this.canvasRect) {
                e.touches = e.touches.map(item => {
                    return {
                        ...item,
                        x: item.clientX - this.canvasRect.left,
                        y: item.clientY - this.canvasRect.top,
                    }
                })
            }
            return e
        },
        touchStart(e) {
            if (!this.canvasEl) return
            this.isStart = true
            // 微信小程序PC端不支持事件，使用这方法模拟一下
            if (this.isPC) {
                getRect(`#${this.canvasId}`, {
                    context: this
                }).then(res => {
                    this.canvasRect = res
                    this.canvasEl.dispatchEvent('touchstart', wrapEvent(this.getTouch(e)))
                })
                return
            }
            this.canvasEl.dispatchEvent('touchstart', wrapEvent(e))
        },
        touchMove(e) {
            if (!this.canvasEl || !this.isStart && this.canvasEl) return
            this.canvasEl.dispatchEvent('touchmove', wrapEvent(this.getTouch(e)))
        },
        touchEnd(e) {
            if (!this.canvasEl) return
            this.isStart = false
            this.canvasEl.dispatchEvent('touchend', wrapEvent(e))
        },
        // #endif
    }
}
</script>
<!-- #ifdef APP-VUE -->
<script module="sign" lang="renderjs">
	import sign from './render'
	export default sign
</script>
<!-- #endif -->
<style lang="scss">
.lime-signature,
.lime-signature__canvas {
    /* #ifndef APP-NVUE */
    position: relative;
    width: 100%;
    height: 100%;
    /* #endif */
    /* #ifdef APP-NVUE */
    flex: 1;
    /* #endif */
}

.mask {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
}

.offscreen {
    position: fixed;
    top: 0;
    // left: 0;
    pointer-events: none;
    // background: rgba(0,255,0,0.5);
    left: 9999px;
}
</style>